﻿// "Therefore those skilled at the unorthodox
// are infinite as heaven and earth,
// inexhaustible as the great rivers.
// When they come to an end,
// they bagin again,
// like the days and months;
// they die and are reborn,
// like the four seasons."
// 
// - Sun Tsu,
// "The Art of War"


using System;
using System.IO;
using System.IO.Compression;
using System.Runtime.Serialization;
using System.Xml;
using System.Xml.Serialization;

namespace Infrastructure.Common
{
    /// <summary>
    /// Helper for serializing and deserializing object.
    /// </summary>
    public static class SerializationUtils
    {
        /// <summary>
        /// Serialize the given object.
        /// </summary>
        /// <typeparam name="T">the type of the object been serialized</typeparam>
        /// <param name="obj">the object to serialize</param>
        /// <param name="omitNamespaces">Optional: true to omit the XML namespaces; otherwise false. The default is false, an XML namespaces are written.</param>
        /// <returns>the serialization data</returns>
        /// <exception cref="SerializationException">thrown on serialization error</exception>
        public static Stream SerializeStream<T>(T obj, bool omitNamespaces = false) where T : class 
        {
            if( obj == null ) throw new ArgumentNullException( "obj" );

            try
            {
                var namespaces = new XmlSerializerNamespaces();
                if (omitNamespaces)
                {
                    namespaces.Add(string.Empty, string.Empty);
                }

                var stream = new MemoryStream();
                var serializer = new XmlSerializer(typeof(T));
                serializer.Serialize(stream, obj, namespaces);
                return stream;
            }
            catch( Exception ex )
            {
                throw new SerializationException(string.Format("Serialization of type [{0}] failed", typeof(T)), ex);
            }
        }

        /// <summary>
        /// Serialize the given object.
        /// </summary>
        /// <typeparam name="T">the type of the object been serialized</typeparam>
        /// <param name="obj">the object to serialize</param>
        /// <param name="omitNamespaces">Optional: true to omit the XML namespaces; otherwise false. The default is false, an XML namespaces are written.</param>
        /// <returns>the serialization data</returns>
        /// <exception cref="SerializationException">thrown on serialization error</exception>
        public static string SerializeString<T>(T obj, bool omitNamespaces = false ) where T : class 
        {
            if (obj == null) throw new ArgumentNullException("obj");

            try
            {
                var namespaces = new XmlSerializerNamespaces();
                if( omitNamespaces )
                {
                    namespaces.Add( string.Empty, string.Empty );
                }

                var buffer = new StringWriter();
                using( var writter = XmlWriter.Create( buffer, new XmlWriterSettings{OmitXmlDeclaration = true} ) )
                {
                    var serializer = new XmlSerializer( typeof(T) );
                    serializer.Serialize(writter, obj, namespaces);
                }
                return buffer.ToString();
            }
            catch( Exception ex )
            {
                throw new SerializationException( string.Format( "Serialization of type [{0}] failed", typeof(T) ), ex );
            }
        }

        /// <summary>
        /// Serialize the given object.
        /// </summary>
        /// <typeparam name="T">the type of the object been serialized</typeparam>
        /// <param name="obj">the object to serialize</param>
        /// <param name="omitNamespaces">Optional: true to omit the XML namespaces; otherwise false. The default is false, an XML namespaces are written.</param>
        /// <returns>the serialization data</returns>
        /// <exception cref="SerializationException">thrown on serialization error</exception>
        public static byte[] Serialize<T>(T obj, bool omitNamespaces = false) where T : class 
        {
            if (obj == null) throw new ArgumentNullException("obj");

            try
            {
                var namespaces = new XmlSerializerNamespaces();
                if (omitNamespaces)
                {
                    namespaces.Add(string.Empty, string.Empty);
                }

                using (var stream = new MemoryStream())
                {
                    var serializer = new XmlSerializer(typeof(T));
                    serializer.Serialize( stream, obj, namespaces );
                    return stream.ToArray();
                }
            }
            catch( Exception ex )
            {
                throw new SerializationException(string.Format("Serialization of type [{0}] failed", typeof(T)), ex);
            }
        }

        /// <summary>
        /// Deserialize the given data to object of type T.
        /// </summary>
        /// <typeparam name="T">the type of the object been deserialized</typeparam>
        /// <param name="data">the object serialized data</param>
        /// <returns>created object of type T</returns>
        /// <exception cref="SerializationException">thrown on deserialization error</exception>
        public static T Deserialize<T>(Stream data) where T : class 
        {
            if (data == null) throw new ArgumentNullException("data");

            try
            {
                var serializer = new XmlSerializer(typeof(T));
                var sObj = (T)serializer.Deserialize( data );
                return sObj;
            }
            catch( Exception ex )
            {
                throw new SerializationException(string.Format("Deserialization of type [{0}] failed", typeof(T)), ex);
            }
        }

        /// <summary>
        /// Deserialize the given data to object of type T.
        /// </summary>
        /// <typeparam name="T">the type of the object been deserialized</typeparam>
        /// <param name="data">the object serialized data</param>
        /// <returns>created object of type T</returns>
        /// <exception cref="SerializationException">thrown on deserialization error</exception>
        public static T Deserialize<T>(string data) where T : class 
        {
            if( data == null ) throw new ArgumentNullException( "data" );
            if( data.Equals( String.Empty ) ) throw new ArgumentException( "Empty String", "data" );

            try
            {
                T sObj;
                using (var stream = new StringReader(data))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    sObj = (T)serializer.Deserialize(stream);
                }
                return sObj;
            }
            catch( Exception ex )
            {
                throw new SerializationException(string.Format("Deserialization of type [{0}] failed", typeof(T)), ex);
            }
        }

        /// <summary>
        /// Deserialize the given data to object of type T.
        /// </summary>
        /// <typeparam name="T">the type of the object been deserialized</typeparam>
        /// <param name="data">the object serialized data</param>
        /// <returns>created object of type T</returns>
        /// <exception cref="SerializationException">thrown on deserialization error</exception>
        public static T Deserialize<T>(byte[] data) where T : class 
        {
            if( data == null ) throw new ArgumentNullException( "data" );
            if( data.Length <= 0 ) throw new ArgumentException( "Empty Array", "data" );

            try
            {
                T sObj;
                using (Stream stream = new MemoryStream(data))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    sObj = (T)serializer.Deserialize(stream);
                }
                return sObj;
            }
            catch( Exception ex )
            {
                throw new SerializationException(string.Format("Deserialization of type [{0}] failed", typeof(T)), ex);
            }
        }

        /// <summary>
        /// Deserialize the given data to object of type T.
        /// the data is zipped using GZipStream so it is used in deserialization.
        /// </summary>
        /// <typeparam name="T">the type of the object been deserialized</typeparam>
        /// <param name="data">the object serialized data zipped</param>
        /// <returns>created object of type T</returns>
        /// <exception cref="SerializationException">thrown on deserialization error</exception>
        public static T DeserializeZipped<T>(byte[] data) where T : class 
        {
            if (data == null) throw new ArgumentNullException("data");
            if (data.Length <= 0) throw new ArgumentException("Empty Array", "data");

            try
            {
                T sObj;
                using (Stream memStream = new MemoryStream(data))
                using (GZipStream zipStream = new GZipStream(memStream, CompressionMode.Decompress))
                {
                    var serializer = new XmlSerializer(typeof(T));
                    sObj = (T)serializer.Deserialize(zipStream);
                }
                return sObj;
            }
            catch (Exception ex)
            {
                throw new SerializationException(string.Format("Deserialization of type [{0}] failed", typeof(T)), ex);
            }
        }
    }
}